/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.commands;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.handlers.commands.CommandHandlersUtil.ValidationException;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.commands.XREDeleteCommand;
import com.hercules.android.receiver.model.objects.XRECommandSequence;
import com.hercules.android.receiver.model.objects.XREEventHandler;
import com.hercules.android.receiver.model.objects.XREFont;
import com.hercules.android.receiver.model.objects.XREHTMLText;
import com.hercules.android.receiver.model.objects.XREObject;
import com.hercules.android.receiver.model.objects.XREResource;
import com.hercules.android.receiver.model.objects.XRESound;
import com.hercules.android.receiver.model.objects.XREStylesheet;
import com.hercules.android.receiver.model.objects.XREText;
import com.hercules.android.receiver.model.objects.XRETextInput;
import com.hercules.android.receiver.model.objects.XREView;
import com.hercules.android.receiver.painting.XREObjectPainterException;
import com.hercules.android.receiver.painting.resources.XRESoundUtil;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * The handler used to process XREDeleteCommand. It will delete the specified XREObject and paint affected views. It
 * extends XREObjectCommandHandler for convenience.
 * <p>
 * <b>Sample Usage:</b> Please look at sample usage of {@link XREAggregateCommandHandler}.
 * <p>
 * <b>Thread Safety:</b> This class is not thread safe since it has mutable fields. But if they are not changed after
 * first configured, then this class is used in a thread safe manner.
 *
 * @author liuliquan, 7realm
 * @version 1.0
 */
public class XREDeleteCommandHandler extends XREObjectCommandHandler {
    /**
     * Default empty constructor.
     */
    public XREDeleteCommandHandler() {
        // empty
    }

    /**
     * Handle given DELETE command message. The target resource or view (with its children views) will be deleted. The
     * affected views will be repainted.
     *
     * @param command the XRE DELETE command to be handled
     * @param receiver the receiver for the XRE application
     * @throws IllegalArgumentException if given command or receiver is null
     * @throws XREViewFinderUtilityException if any error occurs while finding the affected views
     * @throws XREObjectNotFoundException if the XREObject to be deleted is not found
     * @throws XREObjectCommandHandlerException if any other error occurs while handling the command
     */
    @SuppressWarnings("boxing")
    @Override
    protected void processCommandMessage(XRECommand command, AndroidReceiver receiver)
        throws XREObjectCommandHandlerException {
        String methodName = "processCommandMessage";
        LogUtil.enter(getLogger(), this, methodName, command, receiver);

        try {
            // check arguments
            XREDeleteCommand deleteCommand = CommandHandlersUtil.checkCommand(command, XREDeleteCommand.class);
            CommandHandlersUtil.checkReceiver(receiver);

            // call pre painting handler
            callPrePaintHandler(receiver, command);

            // get target ID to be deleted
            int targetId = deleteCommand.getTargetId();

            // check if object is present in ApplicationData
            Map<Integer, XREObject> elementsMap = receiver.getApplicationData().getElementsXREIdToObject();
            if (!elementsMap.containsKey(targetId)) {
                throw new XREObjectNotFoundException("XREObject with given target ID is not found.", targetId);
            }

            // use affectedViewFinder utility to find affected views
            List<XREView> affectedViews = getAffectedViewFinder().getAffectedViews(deleteCommand, receiver);

            // remove target XREObject from ApplicationData
            XREObject removedXREObject = elementsMap.remove(targetId);
            if (removedXREObject == null) {
                throw new XREObjectCommandHandlerException("Target XREObject with ID '" + targetId + "' is null.");
            }
            
            // further process removed object depending on its type
            if (removedXREObject instanceof XREView) {
                removeXREView(elementsMap, (XREView) removedXREObject);
            } else if (removedXREObject instanceof XREResource) {
                removeResource(elementsMap, (XREResource) removedXREObject);

                try {
                    // AndroidAssembly2 added support for XRESound finilization
                    // finalize XRESound
                    if (removedXREObject instanceof XRESound) {
                        XRESound xreSound = (XRESound) removedXREObject;
                        XRESoundUtil.finish(receiver, xreSound);
                    }
                } catch (XREObjectPainterException e) {
                    throw new XREObjectCommandHandlerException("Error occurred while finalizing XRESound "
                        + removedXREObject.getId() + ".", e);
                }
            } else if (removedXREObject instanceof XRECommandSequence) {
                removeCommandSequence(elementsMap, (XRECommandSequence) removedXREObject);
            }

            // paint affect views
            paintAffectedViews(affectedViews, deleteCommand, receiver);
            LogUtil.exit(getLogger(), this, methodName);
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        } catch (XREObjectCommandHandlerException e) {
            throw LogUtil.error(getLogger(), this, methodName, e);
        } catch (ValidationException e) {
            throw LogUtil.error(getLogger(), this, methodName, new XREObjectCommandHandlerException(e.getMessage(), e));
        }
    }

    /**
     * Removes {@link XREView} from parent children list and removes all children.
     *
     * @param elementsMap the XREObject map in ApplicationData
     * @param removedXREView the removed {@link XREView}
     * @throws XREObjectCommandHandlerException if children property of the view is not correct
     */
    private static void removeXREView(Map<Integer, XREObject> elementsMap, XREView removedXREView)
        throws XREObjectCommandHandlerException {
        // remove from parent's children list
        if (removedXREView.getParent() != null) {
            List<XREView> parentChildren = removedXREView.getParent().getChildren();
            if (parentChildren == null) {
                throw new XREObjectCommandHandlerException("Children property of removed XREView's parent is null.");
            }
            parentChildren.remove(removedXREView);
            removedXREView.setParent(null);
        }
        
        // remove all children
        removeChildren(elementsMap, removedXREView);
    }

    /**
     * Removes {@link XREResource} from all XREViews.
     *
     * @param elementsMap the XREObject map in ApplicationData
     * @param removedXREResource the removed {@link XREResource}
     */
    private static void removeResource(Map<Integer, XREObject> elementsMap, XREResource removedXREResource) {
    	Iterator<Integer> pos = removedXREResource.getAffectedViewIDs().iterator();
    	while (pos.hasNext()) {
    		int viewID = pos.next();
    		XREView affectedView = (XREView) elementsMap.get(viewID);
    		if (affectedView != null) {
            	XREResource xreResource = affectedView.getResource();
            	if (removedXREResource instanceof XREFont || removedXREResource instanceof XREStylesheet) {
            		if (xreResource instanceof XREText) {
                        XREText xreText = (XREText) xreResource;

                        // remove font from XREText
                        if (xreText.getFont() == removedXREResource) {
                            xreText.setFont(null);
                        }
                	} else if (xreResource instanceof XRETextInput) {
                        XRETextInput xreTextInput = (XRETextInput) xreResource;

                        // remove font from XRETextInput
                        if (xreTextInput.getFont() == removedXREResource) {
                            xreTextInput.setFont(null);
                        }
                    } else if (xreResource instanceof XREHTMLText) {
                        XREHTMLText xreHtmlText = (XREHTMLText) xreResource;

                        // remove stylesheet from XREHTMLText
                        if (xreHtmlText.getStylesheet() == removedXREResource) {
                            xreHtmlText.setStylesheet(null);
                        }
                    }
            	} else {
            		if (affectedView.getResource() == removedXREResource) {
        				affectedView.setResource(null);
        			}
        			
        			if (affectedView.getMask() == removedXREResource) {
        				affectedView.setMask(null);
                    }
            	}
    		} else {
    			pos.remove();
    		}
    	}
    }

    /**
     * Removes {@link XRECommandSequence} from all event handlers.
     *
     * @param elementsMap the XREObject map in ApplicationData
     * @param removedXRECommandSequence the removed {@link XRECommandSequence}
     */
    private static void removeCommandSequence(Map<Integer, XREObject> elementsMap,
        XRECommandSequence removedXRECommandSequence) {
        for (XREObject xreObject : elementsMap.values()) {
            // skip null objects
            if (xreObject == null || xreObject.getEventHandlers() == null) {
                continue;
            }

            // safely remove EventHandler from map
            Iterator<Entry<String, XREEventHandler>> i = xreObject.getEventHandlers().entrySet().iterator();
            while (i.hasNext()) {
                Entry<String, XREEventHandler> entry = i.next();
                if (entry.getValue() == null) {
                    continue;
                }

                if (entry.getValue().getCommandSequence() == removedXRECommandSequence) {
                    i.remove();
                }
            }
        }
    }

    /**
     * Remove all children views from ApplicationData.
     *
     * @param elementsMap the XREObject map in ApplicationData
     * @param removedXREView the removed XREView
     * @throws XREObjectCommandHandlerException if removed XREView contains null child
     */
    @SuppressWarnings("boxing")
    private static void removeChildren(Map<Integer, XREObject> elementsMap, XREView removedXREView)
        throws XREObjectCommandHandlerException {
        if (removedXREView.getChildren() == null) {
            return;
        }
        for (XREView child : removedXREView.getChildren()) {
            if (child == null) {
                throw new XREObjectCommandHandlerException("XREView '" + removedXREView + "' contains null child.");
            }
            // remove from map
            elementsMap.remove(child.getId());

            child.setParent(null);
            
            // remove children
            removeChildren(elementsMap, child);
        }
    }
}